Skip to content

Drop Windows PowerShell's echo of the lines the client sends (v1.64.1) - #142

Merged
gmpassos merged 9 commits into
masterfrom
fix/powershell-echo
Oct 21, 2026
Merged

gmpassos merged 9 commits into
masterfrom
fix/powershell-echo

Conversation

@gmpassos

Copy link
Copy Markdown
Contributor

Problem

In a PowerShell session on a Windows node, every command showed up echoed above its output, marker tail included:

$ ls
ls ; [Console]::Out.Write('__OMNYSHELL_CWD_9e5b2c8e-48'+'e0-42e7-…'+[char]10);[Console]::Out.Flush()
    Diretório: C:\Users\Usuario
…

The node runs 1.63.3; the client is 1.64.0.

Cause

Windows PowerShell reads these sessions' commands from a pipe, and in that mode it writes each line it reads back to stdout. The init line (function prompt { '' }) hides the PS …> prompt but not the echo. The client assumed a piped PowerShell doesn't echo (PowerShellDialect.wrapCommand: "no echo control needed over a pipe"). Earlier client versions showed fragments of the echo (='Continue', ;$o.Flush(), as in the git-missing report). The longer markers since 1.63.4 make whole lines show.

Fix (client-side, works with any node version)

  • New EchoFilter. It queues each line the client sends, and drops the exact echo of the next queued line, with its CRLF or LF, where it starts an output line. Everything else passes through. Bytes that could still be an echo are held until they can be told apart, so an echo split across chunks is recognised; held bytes are released when the command completes.
  • If no echo arrives for the first command, the shell doesn't echo (PowerShell 7 may not). The filter then switches itself off for the session and never delays output.
  • ShellDialect.echoesInput is true for PowerShell only; InteractiveShellController filters stdout before the marker scan, and registers each line it sends.

Tests

  • Unit, echo_filter_test.dart (12 tests): CRLF and LF, order, split chunks, near-misses, line-start only, only once, held bytes released, turning itself off.
  • Controller tests replay PowerShell 5.1's echo: init line and first marker, a command, a split echo, and a non-echoing PowerShell.
  • Integration, list_shortcut_test.dart (tagged windows): real powershell.exe and pwsh.exe sessions through the controller. They assert that neither the commands nor the markers reach the output, for both a ping-marker and a full-marker command. This is what confirms the diagnosis on CI's Windows runner, since 5.1 can't be run on macOS.

Release

  • 1.64.0 is tagged, so this bumps to 1.64.1 (pubspec.yaml, lib/src/version.dart), with a CHANGELOG entry.

🤖 Generated with Claude Code

gmpassos and others added 3 commits October 21, 2026 00:03
Windows PowerShell reading its commands from a pipe writes each line back
to stdout, so every command showed up with its marker tail above its
output. The client now filters the exact echo of each line it sent
(EchoFilter, PowerShell dialect only), recognises it across chunks, and
switches itself off for a PowerShell that does not echo. Client-side:
works with nodes of any version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esume, encodings and over the Hub

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An echo may follow PowerShell's prompt (`PS …> ` until the init line
  runs, `>> ` on continuation); any pending line can match, and a match
  forgets the earlier pending ones whose echo was missed.
- No permanent switch-off: a shell that did not echo the last command
  only stops holding partial lines; whole lines are still checked, and an
  echo turns holding back on.
- Resume path: commandCompleted() runs before the cwd-fetch marker is
  sent, so that line is not mistaken for the completed command's.
- Scan chunks in place (sublistView); copy only when bytes are held.
- A program's own `> ` prompt is never held.
- Dialect resolved once for the filter; stale "never echoed" docs fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

gmpassos and others added 6 commits October 21, 2026 00:19
…Windows)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…script

CI recorded what both PowerShell editions write over the pipe: a prompt
before every line read (`PS <location>> `, then `PS>` once the prompt
function returns ''), then the line echoed, `\n`-terminated. The filter
missed the `PS>` form and passed the prompt that arrives before the next
line is sent ("PS>hello-omny" on Windows CI).

- A line start that is or may become a prompt is held until its line
  completes, kept across command boundaries; echoes after any prompt form
  are dropped with it.
- powershell_pipe_test now asserts that transcript per edition; unit and
  controller tests replay it exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: the filter guessed at prompt shapes (PS>, PS <loc>> , >> ) because
the empty prompt function makes PowerShell print its PS> fallback, and it
matched echoes against a queue of sent lines. That held a nested pwsh's
prompt or a REPL's '>' forever, showed typed-ahead/continuation echoes,
lost echoes of lines sent before the first prompt, and could attribute
held bytes to a finished agent run.

The init line now sets the prompt to CwdMarker.promptSentinel (written in
halves, so its own echo never contains it). Every line PowerShell reads
comes back as one line starting with that exact string, which is dropped
whole; only the init echo after the default prompt is matched by text.
No queue, no heuristics; chunks without a prompt pass through uncopied.
Docs no longer claim the empty prompt hides the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ust handle (Windows)

Both editions, through the controller: no-newline output, large chunked
output, errors (and their display), non-ASCII and very long command lines,
cd, AI agent runs (output, exit 0/1/native), Read-Host input, and
typed-ahead lines. Each asserts that no sentinel, echo or marker reaches
the user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new real-PowerShell session tests showed 5.1 quoting the failing line
in every error, marker code included ("+ Get-Item … ; $__omnyLc=…"), and
for agent runs the exit-code prefix too. The marker must stay on the
command's line, so 256 blanks now separate the command from what the
client adds around it; the ~80-character quote holds only the command.

Tests: the agent failed-cmdlet case checks for protocol text, not the
error itself; input typed into a running command uses a stdin reader
(Read-Host does not read the pipe with stdin redirected).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It linked the throwaway pub cache's hosted/git folders to the real cache.
On Windows those are junctions, and the recursive delete of the throwaway
cache went through them: the runner's real cache lost packages and the AI
tests then failed to load package:http/testing.dart.

Windows now gets no links (the throwaway cache downloads what it needs);
elsewhere links are removed by type without following them, and the temp
directory is kept if any link survives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gmpassos
gmpassos merged commit 66c8212 into master Oct 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant